package com.mingke.java.GroupChat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

/**
 * @author mingke
 * @function 米奇妙妙屋群聊系统的功能实现类，按照模版类完成构建，并实现自己独有的功能
 * @date 2021/11/9
 */
public class GroupChatHandler extends NIOHandlerTemplate{
    //暂存用户名的map
    HashMap<Integer, GroupChatUserInfo> userInfos = new HashMap<>();
    //用户名的数组集合
    String[] userNames = null;

    public GroupChatHandler() {
        //初始化用户名的数组
        userNames = new String[]{"张麻子", "于右人","座山雕","扫地僧","岳不群","任我行",
                "刘青松","小师妹","东方不败","张翼德"};
    }

    @Override
    public void initServerSocket() {
        //定义需要监听的网络端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress(serverPort);
        try {
            //开启服务通道
            serverSocketChannel = ServerSocketChannel.open();
            //监听指定端口
            serverSocketChannel.bind(inetSocketAddress);
            //开启selector
            selector = Selector.open();
            //设置服务通道以非阻塞的形式进行运转
            serverSocketChannel.configureBlocking(false);
            //指定当前selector的监听内容
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }catch(Exception e) {
            e.printStackTrace();
        }
        System.out.println("米奇妙妙屋群聊系统已经启动，欢迎加入群聊");
    }

    @Override
    public boolean listen() {
        boolean result = false;
        try {
            int flag = selector.select();
            result = flag != 0 ? true : false;
        }catch(Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public void checkKey() {
        //获取到被标记的SelectKey
        Set<SelectionKey> keys = selector.selectedKeys();
        //获取迭代器准备遍历SelectKeys
        Iterator<SelectionKey> iterator = keys.iterator();
        //遍历键
        while(iterator.hasNext()) {
            //获取到当前键
            SelectionKey key = iterator.next();
            //从迭代器中移除掉当前这个key，避免重复判断
            iterator.remove();
            if(key.isAcceptable()) {
                doConnect(key);
            }
            if(key.isReadable()) {
                doRead(key);
            }
        }
    }

    @Override
    public void doConnect(SelectionKey key) {
        SocketChannel socketChannel = null;
        try {
            //这个类似于之前的那个accept，会进行阻塞
            //之前的的时阻塞到有连接，现在是有连接才阻塞，所以阻塞也会立刻解除，得到客户端连接的Channel
            socketChannel = serverSocketChannel.accept();
            //设置客户端连接为非阻塞
            socketChannel.configureBlocking(false);
            //设置当前selector关心的内容，需要设置相关缓冲区大小，不然会打印失败
            socketChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(512));
        }catch(Exception e) {
            e.printStackTrace();
        }
        //根据当前通道的哈希得到一个随机的用户名
        String userName = userNames[socketChannel.hashCode() % 10];
        //初始化客户端信息，存储到hashmap中备用
        userInfos.put(socketChannel.hashCode(), new GroupChatUserInfo(userName, socketChannel));
        //将用户名发送给客户端备用
        send(userName.getBytes(StandardCharsets.UTF_8), socketChannel);
        //广播消息到群聊
        String msg = "系统消息@ 客户端："+userName+"加入群聊";
        boardcast(msg, socketChannel);
        System.out.println(msg+", 当前系统总人数："+userInfos.size());
    }

    @Override
    public void doRead(SelectionKey key) {
        //获取与客户端通信的通道
        SocketChannel socketChannel = (SocketChannel) key.channel();
        //获取到进行数据交换的缓冲区
        ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
        //触发消息的客户端
        String usernName = userInfos.get(socketChannel.hashCode()).getUsername();
        //准备广播的消息
        String msg = null;
        try {
            //使用通道读取缓冲区中的数据
            int len = socketChannel.read(byteBuffer);
            if(len == -1) {  //如果客户端已经下线，就释放相应的通道
                msg = "系统消息@ 客户端："+usernName+"已经下线";
                //清除对应的客户端信息
                userInfos.remove(socketChannel.hashCode());
                //释放对应的资源
                socketChannel.finishConnect();
                key.cancel();
                socketChannel.close();
            }else {
                //切换成读入模式
                byteBuffer.flip();
                int bufferLength = byteBuffer.limit();
                byte bytes[] = new byte[bufferLength];
                byteBuffer.get(bytes, 0, bufferLength);
                msg = usernName + ": " + new String(bytes, Charset.forName("UTF-8"));
            }
        }catch(Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println(msg);
            //使用广播的形式将消息发送到其他客户端
            boardcast(msg, socketChannel);
            //清空缓冲区
            byteBuffer.clear();
        }
    }

    @Override
    public void send(byte[] bytes, SocketChannel channel) {
        //构建新的缓冲区，用于存储要发送到客户端的数据
        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
        try {
            //发送信息
            channel.write(byteBuffer);
        }catch(Exception e) {
            e.printStackTrace();
        }finally {
            //清除缓冲区
            byteBuffer.clear();
        }
    }

    //将服务器收到的消息广播到各个客户端
    public void boardcast(String msg, SocketChannel exclude) {
        Set<Integer> keys = userInfos.keySet();
        for(Integer key : keys) {
            //对于除了自身的客户端对象广播消息
            if(key != exclude.hashCode()) {
                //获取到对应的客户端信息
                GroupChatUserInfo userInfo = userInfos.get(key);
                //发送消息到指定客户端
                send(msg.getBytes(StandardCharsets.UTF_8), userInfo.getSocketChannel());
            }
        }
    }
}
